Skip to content

Split ObservableCacheEx.cs into per-family partial classes#1095

Merged
dwcullop merged 11 commits into
reactivemarbles:mainfrom
dwcullop:maint/cache_ext_breakup
Jun 6, 2026
Merged

Split ObservableCacheEx.cs into per-family partial classes#1095
dwcullop merged 11 commits into
reactivemarbles:mainfrom
dwcullop:maint/cache_ext_breakup

Conversation

@dwcullop
Copy link
Copy Markdown
Member

@dwcullop dwcullop commented May 26, 2026

Splits ObservableCacheEx.cs (6,830+ lines, 100+ distinct operator names spanning 290 method bodies) into one partial-class file per operator name (overload set), following the convention already established by ObservableCacheEx.SortAndBind.cs and ObservableCacheEx.VirtualiseAndPage.cs.

This revision addresses the review feedback on the original family-grouping split:

  1. One file per operator name (per overload set), not per family. The earlier 19 family files (ObservableCacheEx.Edit.cs with 27 operator names, etc.) are replaced with per-operator partial files. Finding AddOrUpdate.cs is now exactly that.
  2. ObservableCacheEx.cs restored as a bare partial carrying the canonical class XML doc and the DefaultSortResetThreshold const (shared between Sort and SortBy). Every per-operator partial repeats the same canonical summary (Extensions for dynamic data.) so the documentation never diverges between files and SA1601 is satisfied. ObservableCacheEx.SortAndBind.cs and ObservableCacheEx.VirtualiseAndPage.cs were also updated for consistency.
  3. Private helpers placed after public members within their containing file. Multi-caller private helpers each get their own ObservableCacheEx.{HelperName}.cs file, matching the per-operator pattern for the public surface (per Jake's follow-up review).
  4. #if SUPPORTS_ASYNC_DISPOSABLE lifted to the project level. ObservableCacheEx.AsyncDisposeMany.cs is excluded from compilation via <Compile Remove> in DynamicData.csproj on platforms where SUPPORTS_ASYNC_DISPOSABLE is not defined, instead of wrapping the file body in #if.

This is still a pure file reorganisation. No code, no XML documentation, no comments, and no constants were added, removed, or altered. The byte content of every method body is preserved (verified programmatically against the original). #if SUPPORTS_BINDINGLIST (around the BindingList Bind overloads) is reconstructed inside ObservableCacheEx.Bind.cs.

Private helper files

Each multi-caller private helper lives in its own ObservableCacheEx.{HelperName}.cs file:

Private helper File Used by
Combine (5 overloads) ObservableCacheEx.Combine.cs And, Except, Or, Xor
ForForced (2 overloads) ObservableCacheEx.ForForced.cs Transform, TransformSafe
AdaptSelector ObservableCacheEx.AdaptSelector.cs Group, GroupOnObservable
OnChangeAction (2 overloads) ObservableCacheEx.OnChangeAction.cs OnItemAdded, OnItemRefreshed, OnItemRemoved, OnItemUpdated
TrueFor ObservableCacheEx.TrueFor.cs TrueForAll, TrueForAny
CreateChangeSetTransformer (3 overloads) ObservableCacheEx.CreateChangeSetTransformer.cs TransformManyAsync, TransformManySafeAsync

Single-operator helpers stay with their operator: DefaultResortOnSourceRefresh const remains in ObservableCacheEx.MergeManyChangeSets.cs. The DefaultSortResetThreshold const moves to ObservableCacheEx.cs (the core file) because it is shared between Sort and SortBy.

Files

File Operator Overloads
ObservableCacheEx.cs (class XML doc + DefaultSortResetThreshold const) -
ObservableCacheEx.Adapt.cs Adapt 2
ObservableCacheEx.AdaptSelector.cs (private helper) -
ObservableCacheEx.AddOrUpdate.cs AddOrUpdate 5
ObservableCacheEx.And.cs And 5
ObservableCacheEx.AsObservableCache.cs AsObservableCache 2
ObservableCacheEx.AsyncDisposeMany.cs AsyncDisposeMany (excluded at project level when SUPPORTS_ASYNC_DISPOSABLE is undefined) 1
ObservableCacheEx.AutoRefresh.cs AutoRefresh 2
ObservableCacheEx.AutoRefreshOnObservable.cs AutoRefreshOnObservable 2
ObservableCacheEx.Batch.cs Batch 1
ObservableCacheEx.BatchIf.cs BatchIf 5
ObservableCacheEx.Bind.cs Bind 12
ObservableCacheEx.BufferInitial.cs BufferInitial 1
ObservableCacheEx.Cast.cs Cast 1
ObservableCacheEx.ChangeKey.cs ChangeKey 2
ObservableCacheEx.Clear.cs Clear 3
ObservableCacheEx.Clone.cs Clone 1
ObservableCacheEx.Combine.cs (private helper) -
ObservableCacheEx.Convert.cs Convert 1
ObservableCacheEx.CreateChangeSetTransformer.cs (private helper) -
ObservableCacheEx.DeferUntilLoaded.cs DeferUntilLoaded 2
ObservableCacheEx.DisposeMany.cs DisposeMany 1
ObservableCacheEx.DistinctValues.cs DistinctValues 1
ObservableCacheEx.EditDiff.cs EditDiff 4
ObservableCacheEx.EnsureUniqueKeys.cs EnsureUniqueKeys 1
ObservableCacheEx.Except.cs Except 5
ObservableCacheEx.ExpireAfter.cs ExpireAfter 5
ObservableCacheEx.Filter.cs Filter 4
ObservableCacheEx.FilterImmutable.cs FilterImmutable 1
ObservableCacheEx.FilterOnObservable.cs FilterOnObservable 2
ObservableCacheEx.FinallySafe.cs FinallySafe 1
ObservableCacheEx.Flatten.cs Flatten 1
ObservableCacheEx.FlattenBufferResult.cs FlattenBufferResult 1
ObservableCacheEx.ForEachChange.cs ForEachChange 1
ObservableCacheEx.ForForced.cs (private helper) -
ObservableCacheEx.FullJoin.cs FullJoin 2
ObservableCacheEx.FullJoinMany.cs FullJoinMany 2
ObservableCacheEx.Group.cs Group 5
ObservableCacheEx.GroupOnObservable.cs GroupOnObservable 2
ObservableCacheEx.GroupOnProperty.cs GroupOnProperty 1
ObservableCacheEx.GroupOnPropertyWithImmutableState.cs GroupOnPropertyWithImmutableState 1
ObservableCacheEx.GroupWithImmutableState.cs GroupWithImmutableState 1
ObservableCacheEx.IgnoreSameReferenceUpdate.cs IgnoreSameReferenceUpdate 1
ObservableCacheEx.IgnoreUpdateWhen.cs IgnoreUpdateWhen 1
ObservableCacheEx.IncludeUpdateWhen.cs IncludeUpdateWhen 1
ObservableCacheEx.InnerJoin.cs InnerJoin 2
ObservableCacheEx.InnerJoinMany.cs InnerJoinMany 2
ObservableCacheEx.InvokeEvaluate.cs InvokeEvaluate 1
ObservableCacheEx.LeftJoin.cs LeftJoin 2
ObservableCacheEx.LeftJoinMany.cs LeftJoinMany 2
ObservableCacheEx.LimitSizeTo.cs LimitSizeTo 2
ObservableCacheEx.MergeChangeSets.cs MergeChangeSets 16
ObservableCacheEx.MergeMany.cs MergeMany 2
ObservableCacheEx.MergeManyChangeSets.cs MergeManyChangeSets (+ DefaultResortOnSourceRefresh const) 14
ObservableCacheEx.MergeManyItems.cs MergeManyItems 2
ObservableCacheEx.MonitorStatus.cs MonitorStatus 1
ObservableCacheEx.NotEmpty.cs NotEmpty 1
ObservableCacheEx.OfType.cs OfType 1
ObservableCacheEx.OnChangeAction.cs (private helper) -
ObservableCacheEx.OnItemAdded.cs OnItemAdded 2
ObservableCacheEx.OnItemRefreshed.cs OnItemRefreshed 2
ObservableCacheEx.OnItemRemoved.cs OnItemRemoved 2
ObservableCacheEx.OnItemUpdated.cs OnItemUpdated 2
ObservableCacheEx.Or.cs Or 5
ObservableCacheEx.PopulateFrom.cs PopulateFrom 2
ObservableCacheEx.PopulateInto.cs PopulateInto 3
ObservableCacheEx.QueryWhenChanged.cs QueryWhenChanged 3
ObservableCacheEx.RefCount.cs RefCount 1
ObservableCacheEx.Refresh.cs Refresh 3
ObservableCacheEx.Remove.cs Remove 6
ObservableCacheEx.RemoveKey.cs RemoveKey 2
ObservableCacheEx.RemoveKeys.cs RemoveKeys 1
ObservableCacheEx.RightJoin.cs RightJoin 2
ObservableCacheEx.RightJoinMany.cs RightJoinMany 2
ObservableCacheEx.SkipInitial.cs SkipInitial 1
ObservableCacheEx.Sort.cs Sort 4
ObservableCacheEx.SortBy.cs SortBy 1
ObservableCacheEx.StartWithEmpty.cs StartWithEmpty 7
ObservableCacheEx.StartWithItem.cs StartWithItem 2
ObservableCacheEx.SubscribeMany.cs SubscribeMany 2
ObservableCacheEx.SuppressRefresh.cs SuppressRefresh 1
ObservableCacheEx.Switch.cs Switch 2
ObservableCacheEx.ToCollection.cs ToCollection 1
ObservableCacheEx.ToObservableChangeSet.cs ToObservableChangeSet 2
ObservableCacheEx.ToObservableOptional.cs ToObservableOptional 2
ObservableCacheEx.ToSortedCollection.cs ToSortedCollection 2
ObservableCacheEx.Transform.cs Transform 9
ObservableCacheEx.TransformAsync.cs TransformAsync 6
ObservableCacheEx.TransformImmutable.cs TransformImmutable 1
ObservableCacheEx.TransformMany.cs TransformMany 4
ObservableCacheEx.TransformManyAsync.cs TransformManyAsync 6
ObservableCacheEx.TransformManySafeAsync.cs TransformManySafeAsync 6
ObservableCacheEx.TransformOnObservable.cs TransformOnObservable 2
ObservableCacheEx.TransformSafe.cs TransformSafe 6
ObservableCacheEx.TransformSafeAsync.cs TransformSafeAsync 6
ObservableCacheEx.TransformToTree.cs TransformToTree 1
ObservableCacheEx.TransformWithInlineUpdate.cs TransformWithInlineUpdate 4
ObservableCacheEx.TreatMovesAsRemoveAdd.cs TreatMovesAsRemoveAdd 1
ObservableCacheEx.TrueFor.cs (private helper) -
ObservableCacheEx.TrueForAll.cs TrueForAll 2
ObservableCacheEx.TrueForAny.cs TrueForAny 2
ObservableCacheEx.UpdateIndex.cs UpdateIndex 1
ObservableCacheEx.Watch.cs Watch 1
ObservableCacheEx.WatchValue.cs WatchValue 2
ObservableCacheEx.WhenAnyPropertyChanged.cs WhenAnyPropertyChanged 1
ObservableCacheEx.WhenPropertyChanged.cs WhenPropertyChanged 1
ObservableCacheEx.WhenValueChanged.cs WhenValueChanged 1
ObservableCacheEx.WhereReasonsAre.cs WhereReasonsAre 1
ObservableCacheEx.WhereReasonsAreNot.cs WhereReasonsAreNot 1
ObservableCacheEx.Xor.cs Xor 5

Touched pre-existing files

  • ObservableCacheEx.SortAndBind.cs and ObservableCacheEx.VirtualiseAndPage.cs had their class-level XML summaries replaced with the canonical Extensions for dynamic data. text so all partials carry consistent documentation. No code in those files was changed.
  • DynamicData.csproj gains a conditional <ItemGroup> that excludes ObservableCacheEx.AsyncDisposeMany.cs from compilation when SUPPORTS_ASYNC_DISPOSABLE is not defined.

Verification

The split was generated programmatically with byte-level per-method equality checks against the original. Every public method and every private helper used by the public surface was confirmed to be present in exactly one file. Builds clean on every target framework (netstandard2.0, net462, net6-net10). The full test suite passes (one pre-existing flaky concurrency test in SuspendNotificationsFixture is unrelated and fails on main too).

dwcullop added 5 commits May 25, 2026 22:59
Splits the 6800-line ObservableCacheEx.cs into 24 smaller partial-class files grouped by operator family. Each method (and all of its overloads) lives in exactly one file. No code, comments, or XML documentation is added, removed, or otherwise modified; this is a pure file reorganization. All 2218 tests pass.
Splits the monolithic ObservableCacheEx.cs into 19 smaller partial-class files grouped by operator family. The two pre-existing partials (ObservableCacheEx.SortAndBind.cs, ObservableCacheEx.VirtualiseAndPage.cs) are untouched. Each method (and all of its overloads) lives in exactly one file. No code, XML documentation, comments, preprocessor directives, or constants are added, removed, or otherwise modified. The split was generated programmatically with byte-level per-method equality checks against the original.
Sorts members alphabetically by name within each new partial file. Overloads of the same name preserve their original declaration order. Constants sort before methods. Pre-existing partials (SortAndBind, VirtualiseAndPage) are not modified.
@dwcullop dwcullop enabled auto-merge (squash) May 26, 2026 14:37
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

JakenVeina
JakenVeina previously approved these changes May 30, 2026
Comment thread src/DynamicData/Cache/ObservableCacheEx.Adapt.cs Outdated
Comment thread src/DynamicData/Cache/ObservableCacheEx.Edit.cs Outdated
Comment thread src/DynamicData/Cache/ObservableCacheEx.Combinators.cs Outdated
Comment thread src/DynamicData/Cache/ObservableCacheEx.Query.cs Outdated
@JakenVeina JakenVeina self-requested a review May 30, 2026 02:43
@JakenVeina JakenVeina dismissed their stale review May 30, 2026 02:43

Goddamnit, GitHub.

Copy link
Copy Markdown
Collaborator

@JakenVeina JakenVeina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seriously, goddamnit, GitHub.

dwcullop added 2 commits May 30, 2026 10:26
…oad set)

Addresses PR review feedback:

1. ONE FILE PER OPERATOR NAME (one overload set per file). The previous split
   into 19 family files is replaced with 103 per-operator partial files,
   matching the existing convention set by ObservableCacheEx.SortAndBind.cs
   and ObservableCacheEx.VirtualiseAndPage.cs.

2. BARE ObservableCacheEx.cs FILE restored to carry the canonical class-level
   XML documentation. All partials carry the same canonical class summary
   ('Extensions for dynamic data.') so SA1601 is satisfied and there are no
   divergent per-file class docs. SortAndBind.cs and VirtualiseAndPage.cs
   were also updated for consistency.

3. PRIVATE HELPERS placed AFTER all public members within their containing
   file. Each private helper lives in the alphabetically-first operator file
   that calls it:
     - Combine -> And.cs (also called by Except, Or, Xor)
     - ForForced -> Transform.cs (also called by TransformSafe)
     - AdaptSelector -> Group.cs (also called by GroupOnObservable)
     - OnChangeAction -> OnItemAdded.cs (also called by OnItem* family)
     - TrueFor -> TrueForAll.cs (also called by TrueForAny)
     - CreateChangeSetTransformer -> TransformManyAsync.cs (also called by TransformManySafeAsync)
     - DefaultResortOnSourceRefresh const -> MergeManyChangeSets.cs
     - DefaultSortResetThreshold const -> Sort.cs

The byte content of every method body is preserved (verified programmatically).
#if/#endif preprocessor regions (SUPPORTS_BINDINGLIST in Bind.cs,
SUPPORTS_ASYNC_DISPOSABLE around AsyncDisposeMany) are reconstructed in the
new files.
@dwcullop dwcullop requested a review from JakenVeina May 30, 2026 18:10
dwcullop added a commit to dwcullop/DynamicData that referenced this pull request May 30, 2026
…ad set)

Mirrors the same convention applied to ObservableCacheEx (PR reactivemarbles#1095):

1. ONE FILE PER OPERATOR NAME (one overload set per file). The previous 17
   family files are replaced with 63 per-operator partial files.

2. BARE ObservableListEx.cs FILE restored to carry the canonical class-level
   XML documentation. All partials carry the same canonical class summary
   ('Extensions for ObservableList.') so SA1601 is satisfied and there are
   no divergent per-file class docs.

3. PRIVATE HELPERS placed AFTER all public members within their containing
   file. The 5 private 'Combine' overloads (used by And, Except, Or, Xor)
   are placed at the bottom of And.cs (alphabetically first caller).

The byte content of every method body is preserved (verified programmatically).
@JakenVeina JakenVeina disabled auto-merge June 3, 2026 03:23
Comment thread src/DynamicData/Cache/ObservableCacheEx.And.cs Outdated
Comment thread src/DynamicData/Cache/ObservableCacheEx.AsyncDisposeMany.cs Outdated
Comment thread src/DynamicData/Cache/ObservableCacheEx.Group.cs Outdated
Comment thread src/DynamicData/Cache/ObservableCacheEx.OnItemAdded.cs Outdated
Comment thread src/DynamicData/Cache/ObservableCacheEx.Sort.cs Outdated
Comment thread src/DynamicData/Cache/ObservableCacheEx.Transform.cs Outdated
Comment thread src/DynamicData/Cache/ObservableCacheEx.TransformManyAsync.cs Outdated
Comment thread src/DynamicData/Cache/ObservableCacheEx.TrueForAll.cs Outdated
Per Jake's review feedback, private helpers used by multiple operators get their
own ObservableCacheEx.{HelperName}.cs file, matching the per-operator pattern
established for the public surface.

  Combine                    -> ObservableCacheEx.Combine.cs (from And.cs)
  AdaptSelector              -> ObservableCacheEx.AdaptSelector.cs (from Group.cs)
  OnChangeAction             -> ObservableCacheEx.OnChangeAction.cs (from OnItemAdded.cs)
  ForForced                  -> ObservableCacheEx.ForForced.cs (from Transform.cs)
  CreateChangeSetTransformer -> ObservableCacheEx.CreateChangeSetTransformer.cs (from TransformManyAsync.cs)
  TrueFor                    -> ObservableCacheEx.TrueFor.cs (from TrueForAll.cs)

DefaultSortResetThreshold const moved to ObservableCacheEx.cs (the core file).
Audit found it is used by both Sort and SortBy, contrary to the original PR body.

AsyncDisposeMany #if SUPPORTS_ASYNC_DISPOSABLE wrapping replaced with a project-level
Compile Remove. The file body is unconditionally compiled on supported platforms and
excluded entirely on unsupported ones (net4*).

All extractions are byte-preserving moves with no functional change. Builds clean on
all target frameworks (netstandard2.0, net462, net6-net10). Targeted tests pass.
dwcullop added a commit that referenced this pull request Jun 6, 2026
* Break ObservableListEx.cs into per-family partial classes

Splits the 2900-line ObservableListEx.cs into 17 smaller partial-class files grouped by operator family. Each method (and all of its overloads) lives in exactly one file. The class declaration is changed to partial; no code, comments, or XML documentation is added, removed, or otherwise modified. All 2218 tests pass.

* Rename Pagination to Virtualise and alphabetize list partial members

Renames ObservableListEx.Pagination.cs to ObservableListEx.Virtualise.cs for closer parity with the cache equivalent (ObservableCacheEx.VirtualiseAndPage.cs). Sorts members alphabetically within each new partial file; overloads of the same name preserve their original declaration order.

* Split ObservableListEx.cs partials into one file per operator (overload set)

Mirrors the same convention applied to ObservableCacheEx (PR #1095):

1. ONE FILE PER OPERATOR NAME (one overload set per file). The previous 17
   family files are replaced with 63 per-operator partial files.

2. BARE ObservableListEx.cs FILE restored to carry the canonical class-level
   XML documentation. All partials carry the same canonical class summary
   ('Extensions for ObservableList.') so SA1601 is satisfied and there are
   no divergent per-file class docs.

3. PRIVATE HELPERS placed AFTER all public members within their containing
   file. The 5 private 'Combine' overloads (used by And, Except, Or, Xor)
   are placed at the bottom of And.cs (alphabetically first caller).

The byte content of every method body is preserved (verified programmatically).

* Extract Combine private helpers into their own file

Per Jake's review feedback, the five Combine private helpers (shared by And,
Or, Except, Xor) move from ObservableListEx.And.cs to a dedicated
ObservableListEx.Combine.cs, matching the per-operator pattern established
for the public surface.

Audit confirms Combine is the only multi-caller private helper in
ObservableListEx partials. Byte-preserving move with no functional change.
Library builds clean on all target frameworks.
@dwcullop dwcullop enabled auto-merge (squash) June 6, 2026 17:03
@dwcullop dwcullop merged commit ab5bd6b into reactivemarbles:main Jun 6, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants